package service

import (
	"context"
	"crypto/sha1"
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/pkg/errors"
	"github.com/smartwalle/alipay/v3"
	"gorm.io/gorm"
	"io"
	"io/ioutil"
	"net/http"
	"pay/api/pay"
	"pay/api/wxpay"
	"pay/internal/conf"
	"pay/internal/constant"
	"pay/internal/model"
	"pay/pkg/ecode"
	"pay/pkg/log"
	"pay/pkg/network"
	"pay/pkg/request_key"
	"pay/pkg/util"
	"sort"
	"strings"
	"time"
)

func (s *Service) syncAlipayAccount(ctx context.Context, configs []*model.AlipayToken) (err error) {
	for _, config := range configs {
		if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
			if err = s.dao.LockAlipayTokenByAppID(ctx, config, config.AppId); err != nil {
				return
			}
			var resp *wxpay.AliSignStatusQueryResponse
			if resp, err = s.downstream.QueryAlipayStatus(ctx, config); err != nil {
				return
			}
			targetStatus := uint8(pay.AlipayAccountStatus_value[resp.SignStatusList[0].Status])
			if targetStatus != config.AccountStatus {
				config.AccountStatus = targetStatus
				if err = s.dao.Save(ctx, config); err != nil {
					return
				}
			}
			return
		}); err != nil {
			return
		}
	}
	return
}

func (s *Service) AlipayAuthorize(ctx context.Context, req *pay.AlipayAuthorizeRequest) (
	resp *pay.DefaultResp, err error) {
	var (
		alipayResp  *alipay.OpenAuthTokenAppRsp
		syncConfigs []*model.AlipayToken
	)
	if alipayResp, err = s.downstream.AlipayClient.OpenAuthTokenApp(alipay.OpenAuthTokenApp{
		GrantType: "authorization_code",
		Code:      req.AppAuthCode,
	}); err != nil {
		err = ecode.AppendMessage(constant.ErrRemoteError, "支付宝授权失败: %s", err.Error())
		return
	}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		for _, token := range alipayResp.Content.Tokens {
			tokenConfig := &model.AlipayToken{}
			if err = s.dao.LockAlipayTokenByAppID(ctx, tokenConfig, token.AuthAppId); err != nil {
				if errors.Cause(err) == gorm.ErrRecordNotFound {
					tokenConfig = &model.AlipayToken{
						AppId:         token.AuthAppId,
						UserId:        token.UserId,
						Token:         token.AppAuthToken,
						ExpireTime:    time.Now().Add(time.Duration(token.ExpiresIn) * time.Second),
						RefreshToken:  token.AppRefreshToken,
						AccountStatus: uint8(pay.AlipayAccountStatus_ALIPAY_STATUS_INIT),
						Status:        true,
					}
					if err = s.dao.Create(ctx, tokenConfig); err != nil {
						return
					}
					syncConfigs = append(syncConfigs, tokenConfig)
					return
				}
				return
			} else {
				// 更新
				tokenConfig.UserId = token.UserId
				tokenConfig.Token = token.AppAuthToken
				tokenConfig.ExpireTime = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
				tokenConfig.RefreshToken = token.AppRefreshToken
				tokenConfig.Status = true
				if err = s.dao.Save(ctx, tokenConfig); err != nil {
					return
				}
				switch pay.AlipayAccountStatus(tokenConfig.AccountStatus) {
				case pay.AlipayAccountStatus_ALIPAY_STATUS_INIT:
					// 以前没有执行过签约流程时，需要同步签约状态
					syncConfigs = append(syncConfigs, tokenConfig)
				}
			}
			var configs []*model.AlipayConfig
			if configs, err = s.dao.FindAlipayConfigByAuthorize(ctx,
				tokenConfig.ID, req.Token.SgId, req.PStoreId); err != nil {
				return
			}
			if len(configs) == 0 {
				// 如果模式下无创建有效记录，创建；已创建有效记录，直接跳过即可；规避创建多个授权
				if err = s.dao.Create(ctx, &model.AlipayConfig{
					SgId:     req.Token.SgId,
					PStoreId: req.PStoreId,
					TokenId:  tokenConfig.ID,
					Name:     token.AuthAppId,
					Status:   true,
					Operator: req.Token.EmpId,
				}); err != nil {
					return
				}
			}
		}
		return
	}); err != nil {
		return
	}
	go func() {
		newCtx := request_key.NewContextWithReqID(ctx)
		var newErr error
		defer ecode.AsyncFailOver(newCtx, "支付宝授权后处理", conf.Conf.Env, &newErr)
		newErr = s.syncAlipayAccount(newCtx, syncConfigs)
	}()
	resp = &pay.DefaultResp{Status: true}
	return
}

// 支付宝订单回调
func (s *Service) AlipayCallback(w http.ResponseWriter, req *http.Request, _ map[string]string) {
	ctx := request_key.GenerateNSQContext()
	var (
		body *alipay.TradeNotification
		err  error
	)
	start := time.Now()
	defer ecode.HttpFailOver(ctx, req, conf.Conf.Env, start, network.ExtractClientIp(req), &err)
	body, err = s.downstream.AlipayClient.GetTradeNotification(req)
	if err != nil {
		log.FromContext(ctx).Warnw("alipay",
			"status_code", "Fail-Unknown",
			"method", "notify",
			"path", req.URL.Path,
			"error", err.Error(),
		)
		return
	}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		order := &model.Order{}
		if err = s.dao.LockTradeByOrderNumber(ctx, order, body.OutTradeNo); err != nil {
			return
		}
		switch pay.PayOrderStatus(order.Status) {
		case pay.PayOrderStatus_PAY_ORDER_STATUS_INIT, pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING:
			switch body.TradeStatus {
			case "TRADE_SUCCESS", "TRADE_FINISHED":
				// 支付成功
				order.TradeNo = body.TradeNo
				order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS)
				var amount, mdiscount, pdiscount int64
				if amount, err = util.FromYuanStrToCent(body.TotalAmount); err != nil {
					return
				}
				if order.Amount != uint64(amount) {
					err = fmt.Errorf("付款金额与返回值不匹配")
					return
				}
				if body.FundBillList != "" {
					var fundBillList []alipay.FundBill
					if err = json.Unmarshal([]byte(body.FundBillList), &fundBillList); err != nil {
						return
					}
					for _, fundDetail := range fundBillList {
						if fundDetail.FundChannel == "MDISCOUNT" {
							if mdiscount, err = util.FromYuanStrToCent(fundDetail.Amount); err != nil {
								return
							}
							order.MerchantDiscount += uint64(mdiscount)
						} else if fundDetail.FundChannel == "DISCOUNT" {
							if pdiscount, err = util.FromYuanStrToCent(fundDetail.Amount); err != nil {
								return
							}
							order.PlatformDiscount += uint64(pdiscount)
						}
					}
					order.UserPayed = order.Amount - order.MerchantDiscount - order.PlatformDiscount
					order.Receive = order.Amount - order.MerchantDiscount
				}
				if payTime, e := util.ParseTime(body.GmtPayment); e != nil {
					order.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
				} else {
					order.PayTime = sql.NullTime{Valid: true, Time: payTime}
				}
			case "TRADE_CLOSED":
				// 支付关闭
				order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
			default:
				return
			}
			order.TradeRemark, _ = json.Marshal(body)
			if err = s.dao.Save(ctx, order); err != nil {
				return
			}
			if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY), order.ID); err != nil {
				return
			}
			var payMessage json.RawMessage
			result := formatPayResult(order)
			if payMessage, err = json.Marshal(result); err != nil {
				return
			}
			if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicPayFinish, payMessage); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	s.downstream.AlipayClient.AckNotification(w)
	return
}

func verifyByteDanceSign(w http.ResponseWriter, signature, timestamp, nonce, msg string) {
	params := []string{timestamp, nonce, msg, model.BootInfo.ByteDanceCallbackToken}
	sort.Strings(params)
	verifyStr := strings.Join(params, "")
	h := sha1.New()
	_, _ = io.WriteString(h, verifyStr)
	if signature != fmt.Sprintf("%x", h.Sum(nil)) {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
}

// 字节跳动回调地址心跳确认
func (s *Service) ByteDanceVerify(w http.ResponseWriter, req *http.Request, _ map[string]string) {
	ctx := request_key.GenerateNSQContext()
	var err error
	start := time.Now()
	defer ecode.HttpFailOver(ctx, req, conf.Conf.Env, start, network.ExtractClientIp(req), &err)
	if err = req.ParseForm(); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	signature := req.FormValue("signature")
	verifyByteDanceSign(w, signature, req.FormValue("timestamp"), req.FormValue("nonce"),
		req.FormValue("msg"))
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(req.FormValue("echostr")))
	return
}

func writeByteDanceResp(w http.ResponseWriter, isSuccess bool) {
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")
	if isSuccess {
		_, _ = w.Write([]byte("\"err_no\": 0, \"err_tips\": \"success\""))
	} else {
		_, _ = w.Write([]byte("\"err_no\": 1, \"err_tips\": \"failed\""))
	}
}

// 字节跳动订单回调
func (s *Service) ByteDanceCallback(w http.ResponseWriter, req *http.Request, _ map[string]string) {
	ctx := request_key.GenerateNSQContext()
	var (
		err       error
		bytesBody []byte
	)
	start := time.Now()
	defer ecode.HttpFailOver(ctx, req, conf.Conf.Env, start, network.ExtractClientIp(req), &err)

	bytesBody, err = ioutil.ReadAll(req.Body)
	if err != nil {
		err = ecode.WithStack(err)
		return
	}
	rawBody := &model.ByteDanceNotify{}
	if err = json.Unmarshal(bytesBody, rawBody); err != nil {
		err = ecode.WithStack(err)
		return
	}
	verifyByteDanceSign(w, rawBody.Signature, rawBody.Timestamp, rawBody.Nonce, rawBody.Msg)
	switch req.FormValue("type") {
	case "payment":
		body := &model.ByteDanceNotifyPayRemark{}
		remarkJson := []byte(req.FormValue("msg"))
		if err = json.Unmarshal(remarkJson, body); err != nil {
			writeByteDanceResp(w, false)
			return
		}
		if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
			order := &model.Order{}
			if err = s.dao.LockTradeByOrderNumber(ctx, order, body.OutTradeNo); err != nil {
				return
			}
			switch pay.PayOrderStatus(order.Status) {
			case pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING:
				if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY), order.ID); err != nil {
					return
				}
				fallthrough
			case pay.PayOrderStatus_PAY_ORDER_STATUS_INIT:
				// 只有支付成功会推送(字节跳动推送信息补全，不会包含渠道单号等信息，只能留空)
				order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
				order.UserPayed = order.Amount
				order.Receive = order.Amount
				order.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
				order.TradeRemark = remarkJson
				if err = s.dao.Save(ctx, order); err != nil {
					return
				}
				var payMessage json.RawMessage
				result := formatPayResult(order)
				if payMessage, err = json.Marshal(result); err != nil {
					return
				}
				if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicPayFinish, payMessage); err != nil {
					return
				}
			}
			return
		}); err != nil {
			writeByteDanceResp(w, false)
			return
		}
	case "refund":
		body := &model.ByteDanceNotifyRefundRemark{}
		remarkJson := []byte(req.FormValue("msg"))
		if err := json.Unmarshal(remarkJson, body); err != nil {
			writeByteDanceResp(w, false)
			return
		}
		if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
			refund := &model.Refund{}
			if err = s.dao.LockTradeByOrderNumber(ctx, refund, body.OutTradeNo); err != nil {
				return
			}
			switch body.Status {
			case "SUCCESS", "FAIL":
			default:
				return
			}
			switch pay.PayOrderStatus(refund.Status) {
			case pay.PayOrderStatus_REFUND_ORDER_STATUS_REFUNDING:
				if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY),
					refund.ID); err != nil {
					return
				}
				fallthrough
			case pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT:
				// 只有支付成功会推送(字节跳动推送信息补全，不会包含渠道单号等信息，只能留空)
				switch body.Status {
				case "SUCCESS":
					refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS)
					refund.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
				case "FAIL":
					refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
					refund.FailedReason = fmt.Sprintf("抖音退款调用失败")
				default:
					return
				}
				refund.TradeRemark = remarkJson
				if err = s.dao.Save(ctx, refund); err != nil {
					return
				}
				var (
					order         *model.Order
					refundMessage json.RawMessage
				)
				if order, err = s.dao.GetOrderByOrderId(ctx, refund.ParentId); err != nil {
					return
				}
				if refundMessage, err = json.Marshal(formatRefundOrderView(order, refund)); err != nil {
					err = ecode.WithStack(err)
					return
				}
				if err = s.dao.PublishUniqueNSQMessage(
					ctx, constant.TopicRefundFinish, refundMessage); err != nil {
					return
				}
			}
			return
		}); err != nil {
			writeByteDanceResp(w, false)
			return
		}
	}
	writeByteDanceResp(w, true)
	return
}
